﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.Configuration;
using AutoMapper.Internal;

namespace Simple.Common.AutoMapper
{
    /// <summary>
    /// 对象扩展。
    /// </summary>
    public static class Mapper
    {
        private static readonly MapperConfigurationExpression mapperCe = new MapperConfigurationExpression
        {
            AllowNullCollections = true,
            AllowNullDestinationValues = true,
            EnableNullPropagationForQueryMapping = true,
            DestinationMemberNamingConvention = new PascalCaseNamingConvention()
        };
        /// <summary>
        /// 配置 AutoMapper，仅启动时配置有效。
        /// </summary>
        /// <param name="configure">服务配置。</param>
        public static void Config(Action<IMapperConfigurationExpression> configure)
        {
            if (configure is null)
            {
                throw new ArgumentNullException(nameof(configure));
            }

            configure.Invoke(mapperCe);
        }
        /// <summary>
        /// 类型映射。
        /// </summary>
        /// <typeparam name="TDestination">映射目标。</typeparam>
        /// <param name="source">原数据。</param>
        /// <param name="useGlobalConfigurationsOnly">仅映射全局配置的关系；为true时，未配置时，抛异常；否则，自动实现映射关系。</param>
        /// <returns></returns>
        public static TDestination Map<TDestination>(this object source, bool useGlobalConfigurationsOnly = false)
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            return useGlobalConfigurationsOnly ? DefaultMapper.Instance.Map<TDestination>(source) : RuntimeMapper.Map<TDestination>(source);
        }
        /// <summary>
        /// 类型映射。
        /// </summary>
        /// <typeparam name="TDestination">映射目标。</typeparam>
        /// <param name="source">原数据。</param>
        /// <param name="useGlobalConfigurationsOnly">仅映射全局配置的关系；为true时，未配置时，抛异常；否则，自动实现映射关系。</param>
        /// <returns></returns>
        public static List<TDestination> Map<TDestination>(this IEnumerable source, bool useGlobalConfigurationsOnly = false)
            => Map<List<TDestination>>((object)source, useGlobalConfigurationsOnly);
        private class DefaultMapper
        {
            static DefaultMapper() { }

            public static readonly global::AutoMapper.Mapper Instance = new global::AutoMapper.Mapper(new MapperConfiguration(mapperCe));
        }
        /// <summary>
        /// 
        /// </summary>
        private  sealed class RuntimeMapper : DefaultMapper
        {
            private static readonly global::AutoMapper.Mapper mapper;
            private static readonly MapperConfiguration mapperConfig;
            private static readonly Func<MapperConfiguration, LockingConcurrentDictionary<TypePair, TypeMap>> resolvedMapsFactory;
            private static readonly ConcurrentDictionary<(Type, Type), bool> MapperCache = new ConcurrentDictionary<(Type, Type), bool>();
            static RuntimeMapper()
            {
                var type = typeof(MapperConfiguration);

                var resolvedMapsField = type.GetField("_resolvedMaps", BindingFlags.Instance | BindingFlags.NonPublic);

                var paramterEx = Expression.Parameter(type);

                var bodyEx = Expression.Field(paramterEx, resolvedMapsField);

                var lambdaEx = Expression.Lambda<Func<MapperConfiguration, LockingConcurrentDictionary<TypePair, TypeMap>>>(bodyEx, paramterEx);

                resolvedMapsFactory = lambdaEx.Compile();

                mapper = new global::AutoMapper.Mapper(mapperConfig = new MapperConfiguration(mapperCe));
            }
            /// <summary>
            /// 类型映射。
            /// </summary>
            /// <typeparam name="TDestination">映射目标。</typeparam>
            /// <param name="source">原数据。</param>
            /// <returns></returns>
            public static TDestination Map<TDestination>(object source)
            {
                return MapperCache.GetOrAdd((source.GetType(), typeof(TDestination)), MapFactory) ? Instance.Map<TDestination>(source) : mapper.Map<TDestination>(source);
            }
            private static bool MapFactory(ValueTuple<Type, Type> tuple)
            {
                var sourceType = tuple.Item1;
                var destinationType = tuple.Item2;
                //? 类型拆解。
            label_unbox_sourceType:

                while (sourceType is { } && sourceType.IsArray)
                {
                    sourceType = sourceType.GetElementType();
                }

                if (sourceType is { } && sourceType.IsGenericType)
                {
                    foreach (var interfaceType in sourceType.GetInterfaces())
                    {
                        if (!interfaceType.IsGenericType) continue;
                        var typeDefinition = interfaceType.GetGenericTypeDefinition();

                        if (typeDefinition != typeof(IList<>) && typeDefinition != typeof(IReadOnlyList<>) &&
                            typeDefinition != typeof(IReadOnlyCollection<>) &&
                            typeDefinition != typeof(ICollection<>) &&
                            typeDefinition != typeof(IEnumerable<>)) continue;
                        sourceType = interfaceType.GetGenericArguments()[0];

                        goto label_unbox_sourceType;
                    }
                }
                label_unbox_destinationType:

                while (destinationType is { } && destinationType.IsArray)
                {
                    destinationType = destinationType.GetElementType();
                }

                if (destinationType is { } && destinationType.IsGenericType)
                {
                    foreach (var interfaceType in destinationType.GetInterfaces())
                    {
                        if (!interfaceType.IsGenericType) continue;
                        var typeDefinition = interfaceType.GetGenericTypeDefinition();

                        if (typeDefinition != typeof(IList<>) && typeDefinition != typeof(IReadOnlyList<>) &&
                            typeDefinition != typeof(IReadOnlyCollection<>) &&
                            typeDefinition != typeof(ICollection<>) &&
                            typeDefinition != typeof(IEnumerable<>)) continue;
                        destinationType = interfaceType.GetGenericArguments()[0];

                        goto label_unbox_destinationType;
                    }
                }
                var typePair = new TypePair(sourceType, destinationType);
                var resolvedMaps = resolvedMapsFactory.Invoke(mapperConfig);
                //? 优先使用自定义的映射关系。
                if (resolvedMaps.ContainsKey(typePair))
                {
                    return true;
                }
                //? 泛型定义。
                if (typePair.IsGeneric)
                {
                    typePair = new TypePair(
                        sourceType.IsGenericType ? sourceType.GetGenericTypeDefinition() : sourceType,
                        destinationType.IsGenericType ? destinationType.GetGenericTypeDefinition() : destinationType
                    );
                    if (resolvedMaps.ContainsKey(typePair))
                    {
                        return true;
                    }
                }
                //? 取全局默认配置。
                var profile = mapperConfig.Profiles.First();
                var sourceDetails = profile.CreateTypeDetails(sourceType);
                var destinationDetails = profile.CreateTypeDetails(destinationType);
                var config = new MappingExpression(typePair, MemberList.Destination);
                var typeMap = new TypeMap(sourceDetails, destinationDetails, profile);
                foreach (var memberInfo in destinationDetails.PublicWriteAccessors)
                {
                    var resolvers = new LinkedList<MemberInfo>();

                    if (profile.MapDestinationPropertyToSource(sourceDetails, memberInfo.DeclaringType, memberInfo.GetMemberType(), memberInfo.Name, resolvers, false))
                    {
                        typeMap.AddPropertyMap(memberInfo, resolvers);
                    }
                }
                config.Configure(typeMap);
                mapperConfig.RegisterTypeMap(typeMap);
                resolvedMaps[typePair] = typeMap;

                typeMap.Seal(mapperConfig);

                return false;
            }
        }
    }
}
